/*
 * Copyright (c) 2021, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.feature.detect.edge;

import boofcv.generate.AutoTypeImage;
import boofcv.generate.CodeGeneratorBase;

import java.io.FileNotFoundException;

/**
 *
 * @author Peter Abeles
 */
public class GenerateGradientToEdgeFeatures extends CodeGeneratorBase {
	@Override
	public void generateCode() throws FileNotFoundException {
		printPreamble();

		printFunctions(AutoTypeImage.F32);
		printFunctions(AutoTypeImage.S16);
		printFunctions(AutoTypeImage.S32);

		printTypeAgnostic();

		out.print("\n" +
				"}\n");
	}

	private void printPreamble() {
		out.print(
				"import boofcv.alg.InputSanityCheck;\n" +
				"import boofcv.alg.feature.detect.edge.impl.*;\n" +
				"import boofcv.concurrency.BoofConcurrency;\n" +
				"import boofcv.struct.image.GrayF32;\n" +
				"import boofcv.struct.image.GrayS16;\n" +
				"import boofcv.struct.image.GrayS32;\n" +
				"import boofcv.struct.image.GrayS8;\n" +
				"\n" +
				"/**\n" +
				" * <p>\n" +
				" * Give the image's gradient in the x and y direction compute the edge's intensity and orientation.\n" +
				" * Two ways are provided for computing the edge's intensity: euclidean norm and sum of absolute values (induced 1-norm).\n" +
				" * The former is the most accurate, while the later is much faster.\n" +
				" * </p>\n" +
				" *\n" +
				" * <p>\n" +
				" * norm: sqrt( g<sub>x</sub><sup>2</sup> + g<sub>y</sub><sup>2</sup>)<br>\n" +
				" * abs: |g<sub>x</sub>| + |g<sub>y</sub>|\n" +
				" * angle: atan( g<sub>y</sub> / g<sub>x</sub> )\n" +
				" * </p>\n" +
				" *\n" +
				" * <p>\n" +
				" * When computing the angle care is taken to avoid divided by zero errors.\n" +
				" * </p>\n" +
				" * <p>\n" +
				" * WARNING: Do not modify. Automatically generated by "+getClass().getSimpleName()+".\n" +
				" * </p>\n" +
				" *\n" +
				" * @author Peter Abeles\n" +
				" */\n" +
				"@SuppressWarnings(\"Duplicates\")\n" +
				"public class "+className+" {\n\n");
	}

	private void printFunctions( AutoTypeImage image ) {
		String imageType = image.getSingleBandName();
		out.print(
				"\t/**\n" +
				"\t * Computes the edge intensity using a Euclidean norm.\n" +
				"\t *\n" +
				"\t * @param derivX Derivative along x-axis. Not modified.\n" +
				"\t * @param derivY Derivative along y-axis. Not modified.\n" +
				"\t * @param intensity Edge intensity.\n" +
				"\t */\n" +
				"\tstatic public void intensityE("+imageType+" derivX , "+imageType+" derivY , GrayF32 intensity )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(derivX,derivY);\n" +
				"\t\tintensity.reshape(derivX.width,derivX.height);\n" +
				"\n" +
				"\t\tif(BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplGradientToEdgeFeatures_MT.intensityE(derivX,derivY,intensity);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplGradientToEdgeFeatures.intensityE(derivX,derivY,intensity);\n" +
				"\t\t}\n" +
				"\t}\n" +
				"\n" +
				"\t/**\n" +
				"\t * Computes the edge intensity using a Euclidean norm.\n" +
				"\t *\n" +
				"\t * @param derivX Derivative along x-axis. Not modified.\n" +
				"\t * @param derivY Derivative along y-axis. Not modified.\n" +
				"\t * @param intensity Edge intensity.\n" +
				"\t */\n" +
				"\tstatic public void intensityAbs("+imageType+" derivX , "+imageType+" derivY , GrayF32 intensity )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(derivX,derivY);\n" +
				"\t\tintensity.reshape(derivX.width,derivX.height);\n" +
				"\n" +
				"\t\tif(BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplGradientToEdgeFeatures_MT.intensityAbs(derivX,derivY,intensity);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplGradientToEdgeFeatures.intensityAbs(derivX,derivY,intensity);\n" +
				"\t\t}\n" +
				"\t}\n" +
				"\n" +
				"\t/**\n" +
				"\t * Computes the edge orientation using the {@link Math#atan} function.\n" +
				"\t *\n" +
				"\t * @param derivX Derivative along x-axis. Not modified.\n" +
				"\t * @param derivY Derivative along y-axis. Not modified.\n" +
				"\t * @param angle Edge orientation in radians (-pi/2 to pi/2).\n" +
				"\t */\n" +
				"\tstatic public void direction("+imageType+" derivX , "+imageType+" derivY , GrayF32 angle )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(derivX,derivY);\n" +
				"\t\tangle.reshape(derivX.width,derivX.height);\n" +
				"\n" +
				"\t\tif(BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplGradientToEdgeFeatures_MT.direction(derivX,derivY,angle);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplGradientToEdgeFeatures.direction(derivX,derivY,angle);\n" +
				"\t\t}\n" +
				"\t}\n" +
				"\n" +
				"\t/**\n" +
				"\t * Computes the edge orientation using the {@link Math#atan2} function.\n" +
				"\t *\n" +
				"\t * @param derivX Derivative along x-axis. Not modified.\n" +
				"\t * @param derivY Derivative along y-axis. Not modified.\n" +
				"\t * @param angle Edge orientation in radians (-pi to pi).\n" +
				"\t */\n" +
				"\tstatic public void direction2("+imageType+" derivX , "+imageType+" derivY , GrayF32 angle )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(derivX,derivY);\n" +
				"\t\tangle.reshape(derivX.width,derivX.height);\n" +
				"\n" +
				"\t\tif(BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplGradientToEdgeFeatures_MT.direction2(derivX,derivY,angle);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplGradientToEdgeFeatures.direction2(derivX,derivY,angle);\n" +
				"\t\t}\n" +
				"\t}\n"+
				"\n" +
				"\t/**\n" +
				"\t * <p>\n" +
				"\t * Sets edge intensities to zero if the pixel has an intensity which is less than any of\n" +
				"\t * the two adjacent pixels. Pixel adjacency is determined based upon the sign of the image gradient. Less precise\n" +
				"\t * than other methods, but faster.\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param intensity Edge intensities. Not modified.\n" +
				"\t * @param derivX Image derivative along x-axis.\n" +
				"\t * @param derivY Image derivative along y-axis.\n" +
				"\t * @param output Filtered intensity. If null a new image will be declared and returned. Modified.\n" +
				"\t * @return Filtered edge intensity.\n" +
				"\t */\n" +
				"\tstatic public GrayF32 nonMaxSuppressionCrude4(GrayF32 intensity , "+imageType+" derivX , "+imageType+" derivY, GrayF32 output )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(intensity,derivX,derivY);\n" +
				"\t\toutput = InputSanityCheck.checkDeclare(intensity,output);\n" +
				"\n" +
				"\t\tif(BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplEdgeNonMaxSuppressionCrude_MT.inner4(intensity, derivX, derivY, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppressionCrude_MT.border4(intensity, derivX, derivY, output);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplEdgeNonMaxSuppressionCrude.inner4(intensity, derivX, derivY, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppressionCrude.border4(intensity, derivX, derivY, output);\n" +
				"\t\t}\n" +
				"\n" +
				"\t\treturn output;\n" +
				"\t}\n\n");
	}

	private void printTypeAgnostic() {
		out.print("\t/**\n" +
				"\t * <p>\n" +
				"\t * Converts an image containing edge angles (-pi/2 to pi/2) into a discrete set of angles.\n" +
				"\t * The conversion is done by rounding the angle to the nearest orientation in the set.\n" +
				"\t * </p>\n" +
				"\t * <p>\n" +
				"\t * Discrete value to angle (degrees): 0=0,1=45,2=90,-1=-45\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param angle Input image containing edge orientations. Orientations are assumed to be\n" +
				"\t * from -pi/2 to pi/2. Not modified.\n" +
				"\t * @param discrete  Output set of discretized angles. Values will be from -1 to 2, inclusive. If null a new\n" +
				"\t * image will be declared and returned. Modified.\n" +
				"\t * @return Discretized direction.\n" +
				"\t */\n" +
				"\tstatic public GrayS8 discretizeDirection4(GrayF32 angle , GrayS8 discrete )\n" +
				"\t{\n" +
				"\t\tdiscrete = InputSanityCheck.checkDeclare(angle,discrete,GrayS8.class);\n" +
				"\n" +
				"\t\tfinal float A = (float)(Math.PI/8.0);\n" +
				"\t\tfinal float B = (float)(Math.PI/4.0);\n" +
				"\t\tfinal int w = angle.width;\n" +
				"\t\tfinal int h = angle.height;\n" +
				"\n" +
				"\t\tfor( int y = 0; y < h; y++ ) {\n" +
				"\t\t\tint indexSrc = angle.startIndex + y*angle.stride;\n" +
				"\t\t\tint indexDst = discrete.startIndex + y*discrete.stride;\n" +
				"\n" +
				"\t\t\tint end = indexSrc + w;\n" +
				"\t\t\tfor( ; indexSrc < end; indexSrc++ , indexDst++ ) {\n" +
				"\t\t\t\tfloat a = angle.data[indexSrc];\n" +
				"\t\t\t\tint val;\n" +
				"\t\t\t\tif( a >= 0 ) {\n" +
				"\t\t\t\t\tval = (int)((a+A)/B);\n" +
				"\t\t\t\t} else {\n" +
				"\t\t\t\t\tval = (int)((a-A)/B);\n" +
				"\t\t\t\t}\n" +
				"\t\t\t\tdiscrete.data[indexDst] = (byte)(val == -2 ? 2 : val);\n" +
				"\t\t\t}\n" +
				"\t\t}\n" +
				"\n" +
				"\t\treturn discrete;\n" +
				"\t}\n" +
				"\n" +
				"\t/**\n" +
				"\t * <p>\n" +
				"\t * Converts an image containing edge angles (-pi to pi) into a discrete set of 8 angles.\n" +
				"\t * The conversion is done by rounding the angle to the nearest orientation in the set.\n" +
				"\t * </p>\n" +
				"\t * <p>\n" +
				"\t * Discrete value to angle (degrees): 0=0,1=45,2=90,3=135,4=180,-1=-45,-2=--90,-3=-135\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param angle Input image containing edge orientations. Orientations are assumed to be\n" +
				"\t * from -pi to pi. Not modified.\n" +
				"\t * @param discrete  Output set of discretized angles. Values will be from -3 to 4, inclusive. If null a new\n" +
				"\t * image will be declared and returned. Modified.\n" +
				"\t * @return Discretized direction.\n" +
				"\t */\n" +
				"\tstatic public GrayS8 discretizeDirection8(GrayF32 angle , GrayS8 discrete )\n" +
				"\t{\n" +
				"\t\tdiscrete = InputSanityCheck.checkDeclare(angle,discrete,GrayS8.class);\n" +
				"\n" +
				"\t\tfinal float A = (float)(Math.PI/8.0);\n" +
				"\t\tfinal float B = (float)(Math.PI/4.0);\n" +
				"\t\tfinal int w = angle.width;\n" +
				"\t\tfinal int h = angle.height;\n" +
				"\n" +
				"\t\tfor( int y = 0; y < h; y++ ) {\n" +
				"\t\t\tint indexSrc = angle.startIndex + y*angle.stride;\n" +
				"\t\t\tint indexDst = discrete.startIndex + y*discrete.stride;\n" +
				"\n" +
				"\t\t\tint end = indexSrc + w;\n" +
				"\t\t\tfor( ; indexSrc < end; indexSrc++ , indexDst++ ) {\n" +
				"\t\t\t\tfloat a = angle.data[indexSrc];\n" +
				"\t\t\t\tint val;\n" +
				"\t\t\t\tif( a >= 0 ) {\n" +
				"\t\t\t\t\tval = (int)((a+A)/B);\n" +
				"\t\t\t\t} else {\n" +
				"\t\t\t\t\tval = (int)((a-A)/B);\n" +
				"\t\t\t\t}\n" +
				"\t\t\t\tdiscrete.data[indexDst] = (byte)(val == -4 ? 4 : val);\n" +
				"\t\t\t}\n" +
				"\t\t}\n" +
				"\n" +
				"\t\treturn discrete;\n" +
				"\t}\n"+
				"\t/**\n" +
				"\t * <p>\n" +
				"\t * Sets edge intensities to zero if the pixel has an intensity which is less than either of\n" +
				"\t * the two adjacent pixels. Pixel adjacency is determined by the gradients discretized direction.\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param intensity Edge intensities. Not modified.\n" +
				"\t * @param direction 4-Discretized direction. See {@link #discretizeDirection4(GrayF32, GrayS8)}. Not modified.\n" +
				"\t * @param output Filtered intensity. If null a new image will be declared and returned. Modified.\n" +
				"\t * @return Filtered edge intensity.\n" +
				"\t */\n" +
				"\tstatic public GrayF32 nonMaxSuppression4(GrayF32 intensity , GrayS8 direction , GrayF32 output )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(intensity,direction);\n" +
				"\t\toutput = InputSanityCheck.checkDeclare(intensity,output);\n" +
				"\n" +
				"\t\tif( BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplEdgeNonMaxSuppression_MT.inner4(intensity, direction, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppression_MT.border4(intensity, direction, output);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplEdgeNonMaxSuppression.inner4(intensity, direction, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppression.border4(intensity, direction, output);\n" +
				"\t\t}\n" +
				"\n" +
				"\t\treturn output;\n" +
				"\t}\n" +
				"\n" +
				"\t/**\n" +
				"\t * <p>\n" +
				"\t * Sets edge intensities to zero if the pixel has an intensity which is less than either of\n" +
				"\t * the two adjacent pixels. Pixel adjacency is determined by the gradients discretized direction.\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param intensity Edge intensities. Not modified.\n" +
				"\t * @param direction 8-Discretized direction. See {@link #discretizeDirection8(GrayF32, GrayS8)}. Not modified.\n" +
				"\t * @param output Filtered intensity. If null a new image will be declared and returned. Modified.\n" +
				"\t * @return Filtered edge intensity.\n" +
				"\t */\n" +
				"\tstatic public GrayF32 nonMaxSuppression8(GrayF32 intensity , GrayS8 direction , GrayF32 output )\n" +
				"\t{\n" +
				"\t\tInputSanityCheck.checkSameShape(intensity,direction);\n" +
				"\t\toutput = InputSanityCheck.checkDeclare(intensity,output);\n" +
				"\n" +
				"\t\tif( BoofConcurrency.USE_CONCURRENT ) {\n" +
				"\t\t\tImplEdgeNonMaxSuppression_MT.inner8(intensity, direction, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppression_MT.border8(intensity, direction, output);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplEdgeNonMaxSuppression.inner8(intensity, direction, output);\n" +
				"\t\t\tImplEdgeNonMaxSuppression.border8(intensity, direction, output);\n" +
				"\t\t}\n" +
				"\n" +
				"\t\treturn output;\n" +
				"\t}\n" +
				"\n");
	}

	public static void main( String[] args ) throws FileNotFoundException {
		GenerateGradientToEdgeFeatures app = new GenerateGradientToEdgeFeatures();
		app.parseArguments(args);
		app.generateCode();
	}
}
